home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Aminet 1 (Walnut Creek)
/
Aminet - June 1993 [Walnut Creek].iso
/
usenet
/
sources
/
volume91
/
news
/
barn_201
/
part02
/
arn.c
next >
Wrap
C/C++ Source or Header
|
1991-02-07
|
44KB
|
1,514 lines
/*
* File Name: arn.c
* Project: BARN - Bah's Amiga ReadNews.
* Purpose: Mainline code for Amiga News Reader.
* Functions: main.
* Author: Jeff Van Epps
* Created: 02 Sep 89
* Last Modified: 15 Jan 91
* Comments:
* barn [config_file]
*
* config_file is optional, defaulting to "barn.config".
*
* History:
* 02 Sep 89/JVE Created.
* 03 Dec 89/JVE Tries to cope with "Re: $x" subject lines when finding
* next article to read and when killing articles.
* 16 Dec 89/JVE Fixed a bug in the last mark in UpdateReadList if the
* last article was still unread.
* 30 Dec 89/JVE Implemented kill-list checking.
* 05 Sep 90/JVE Removed volume references -- run from base directory
* of news articles.
* 12 Sep 90/JVE Added parm to Paginate() to account for headers
* already printed.
* 17 Oct 90/JVE Added '-' option to go back to previous article.
* Made Paginate() pass up the 'K' option.
* Added help option. Added Reply and Followup.
* 19 Oct 90/JVE UNIX (Sun) port. Curses, readdir, etc.
* 20 Oct 90/JVE Added configuration file stuff.
* 22 Oct 90/JVE Use regular expressions for subject following and
* killing.
* 24 Oct 90/JVE Added scan subjects command "=".
* 05 Nov 90/JVE New opendir() and stat() functions from SAS/C used.
* 10 Nov 90/JVE Make sure articles shown in order of posting.
* 11 Nov 90/JVE Added LINES/COLS to config file.
* 14 Nov 90/JVE Added "m" - mark as unread, and use of "n" (next)
* within pager. Added CR/NL to print one more line
* in pager, scanner.
* 16 Nov 90/JVE Scanner fix -- used to always show first article in
* group even if already read.
* 22 Nov 90/JVE End page at form feed.
* 25 Nov 90/JVE Config option to clear screen for each new page.
* 28 Nov 90/JVE Fix to MakePattern to escape backslash in regexp.
* Changed getcmd() to take array of strings for "help"
* to escape compiler string length limitation. Changed
* MakePattern() to allow characters between 'Re:' and
* 'text' being matched.
* 05 Jan 91/JVE Fixed line-wrap calculations in Paginate(). Put last
* line of previous page at top of next page when in
* NO_SCROLL mode. Put 'subject' in article_info as an
* an optimization, so header list didn't have to be
* searched constantly. Added '#' command to go to
* specific article by number.
* 06 Jan 91/JVE Added back-page capability in Paginate(). Changed
* name to "barn" to avoid conflict with another "arn"
* in the Amiga world which performs the same function.
* Changed getcmd() to accept one list of commands which
* gets displayed to the user and one which doesn't.
* Added "a - About BARN" command.
* 15 Jan 91/JVE Avoid regexec() on NULL subject.
*/
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <regexp.h>
# ifdef sun
# include <dirent.h> /* opendir & friends */
# else /* not sun */
# include <sys/types.h>
# include <sys/dir.h>
# endif /* sun */
# include <sys/stat.h>
# include "standard.h"
# include "article.h"
# include "ng.h"
# include "kill.h"
# include "configure.h"
# include "variables.h"
# include "reply.h"
# include "screenstuff.h"
# define BARN_VERSION "BARN version 2.01"
# define DEFAULT_CONFIG "barn.config" /* ARN default configuration file */
# define DEFAULT_NEWSRC ".newsrc" /* default .newsrc filename */
# define DEFAULT_KILL "KILL" /* default name of kill files */
# define KILL "KILL" /* KILL filenames start with this */
# define REPLY_PATTERN "^[RrEe: ]*" /* regex for reply articles */
# define REPLY_REMOVER "^[RrEe: ]*(.*)$" /* regex for removing Re: */
# ifdef sun
/* LINES and COLS are variables set by curses. */
# define Lines LINES
# define Columns COLS
# endif /* sun */
# ifdef sun
# define COPY_CMD "cp"
# define raw( x ) cbreak()
# define cooked( x ) nocbreak()
# define printf printw
# define gets( x ) getstr( (x) )
# define getchar() getch()
# else /* not sun */
# define COPY_CMD "copy"
# endif /* sun */
typedef struct {
long num; /* article number */
ARTICLE_INFO *pointer; /* article structure */
} SORT_INFO;
static char *currentdir; /* current directory on startup */
# ifndef sun
static int Lines, Columns; /* terminal characteristics */
# endif
/*
* Local function which creates the string form and internal form of a
* regular expression. The string form should have all regexp-special
* characters escaped.
*/
struct regexp *MakePattern( char *pattern, char *original );
void ScanSubjects( ARTICLE_INFO *arts );
void main( argc, argv )
int argc;
char **argv;
{
NG_INFO *alreadyread; /* list of newsgroups/articles already read */
NG_INFO *ng; /* ptr for newsgroup list traversal */
KILL_INFO *globalkill; /* entries from global KILL file */
char *config_file; /* name of configuration file to use */
char *newsrc_file; /* name of .newsrc file */
char *global_kill_file; /* name of global kill file */
void Startup(), CheckConfiguration();
extern char *getcwd();
currentdir = getcwd( NULLP( char ), BUFSIZ );
if ( argc < 2 )
config_file = DEFAULT_CONFIG;
else
config_file = argv[1];
Configure( config_file );
CheckConfiguration();
Startup();
/*
* Find all articles we've already read from the .newsrc file.
*/
if ( ( newsrc_file = GetVar( VAR_NEWSRC ) ) == NULL )
newsrc_file = DEFAULT_NEWSRC;
alreadyread = GetNewsRC( newsrc_file );
if ( ( global_kill_file = GetVar( VAR_KILL ) ) == NULL )
global_kill_file = DEFAULT_KILL;
globalkill = GetKillList( global_kill_file );
/*
* For each newsgroup in the .newsrc, handle the newsgroup.
* (Code needs to be added to notice newsgroups not in .newsrc)
*/
for ( ng = alreadyread; ng != NULLP( NG_INFO ); ng = ng->next )
{
chdir( currentdir );
if ( ReadNewsgroup( ng, &globalkill ) )
break;
}
chdir( currentdir );
PutNewsRC( newsrc_file, alreadyread );
PutKillList( global_kill_file, globalkill );
DestroyNGList( alreadyread );
DestroyKillList( globalkill );
cooked( stdin );
# ifdef sun
endwin();
# endif
}
/****************************************************************************/
/* FUNCTION: AboutBARN */
/* */
/* PURPOSE: Print version of BARN. */
/* */
/* INPUT PARAMETERS: */
/* NAME I/O DESCRIPTION */
/* ---- --- ----------- */
/* */
/* RETURNS: none */
/* */
/* COMMENTS: */
/* */
/* HISTORY: */
/* 1. 06 Jan 91 Created. */
/* */
/****************************************************************************/
void AboutBARN()
{
printf( "\n\n%s written by Jeff Van Epps (aka Lord Bah)\n\n", BARN_VERSION );
printf( "raw.c written by Chuck McManis\n" );
printf( "sendpacket.c written by Phil Lindsay, Carolyn Scheppner, and Andy Finkel\n" );
printf( "Uses regexp library written by Henry Spencer, copyrighted by the\n" );
printf( "University of Toronto in 1986.\n" );
printf( "\nExecutable freely redistributable with the restriction that\n" );
printf( "the authors are not responsible for consequences of use.\n" );
printf( "\nAddress comments to amusing!jeffv@bisco.kodak.com or lordbah@cup.portal.com.\n" );
printf( "\nHit any key to continue" );
(void) getchar();
printf( "\n" );
}
/****************************************************************************/
/* FUNCTION: Startup */
/* */
/* PURPOSE: Get ready to run ARN. */
/* */
/* INPUT PARAMETERS: */
/* NAME I/O DESCRIPTION */
/* ---- --- ----------- */
/* */
/* RETURNS: */
/* */
/* COMMENTS: */
/* */
/* HISTORY: */
/* 1. 05 Sep 89 Created. */
/* */
/****************************************************************************/
void Startup()
{
# ifdef sun
(void) initscr();
# endif
raw( stdin );
}
/****************************************************************************/
/* FUNCTION: CheckConfiguration */
/* */
/* PURPOSE: Check to see that user has defined required variables in */
/* the configuration file. */
/* */
/* INPUT PARAMETERS: */
/* NAME I/O DESCRIPTION */
/* ---- --- ----------- */
/* */
/* RETURNS: none */
/* */
/* COMMENTS: */
/* Exits if any of the required variables are not set. */
/* */
/* HISTORY: */
/* 1. 20 Oct 90 Created. */
/* 2. 11 Nov 90 Added LINES & COLS. */
/* */
/****************************************************************************/
static void CheckConfiguration()
{
char *tmp;
if ( GetVar( VAR_USER ) == NULL || GetVar( VAR_NODE ) == NULL ||
GetVar( VAR_DOMAIN ) == NULL || GetVar( VAR_NAME ) == NULL )
{
fprintf( stderr, "Must define \"%s\", \"%s\", \"%s\", and \"%s\" in configuration file.\n",
VAR_USER, VAR_NODE, VAR_DOMAIN, VAR_NAME );
exit( 1 );
}
# ifndef sun
if ( ( ( tmp = GetVar( VAR_LINES ) ) == NULL ) ||
( Lines = atoi( tmp ) ) < 10 ||
Lines > 48 )
{
fprintf( stderr, "Missing/unreasonable '%s' value in config file.\n",
VAR_LINES );
exit( 1 );
}
if ( ( ( tmp = GetVar( VAR_COLS ) ) == NULL ) ||
( Columns = atoi( tmp ) ) < 10 ||
Columns > 133 )
{
fprintf( stderr, "Missing/unreasonable '%s' value in config file.\n",
VAR_COLS );
exit( 1 );
}
# endif
}
/****************************************************************************/
/* FUNCTION: ReadNewsgroup */
/* */
/* PURPOSE: Allow user to read articles within a newsgroup. */
/* */
/* INPUT PARAMETERS: */
/* NAME I/O DESCRIPTION */
/* ---- --- ----------- */
/* ng I/O List of articles read within newsgroup. */
/* gkill I/O Global kill list. */
/* */
/* RETURNS: */
/* TRUE If user wants to quit. */
/* FALSE Otherwise. */
/* */
/* COMMENTS: */
/* */
/* HISTORY: */
/* 1. 04 Sep 89 Created. */
/* 2. 06 Jan 91 Added "a" - About BARN command. */
/* */
/****************************************************************************/
# define READ_CMDS "acnqy "
static char *READ_HELP[] = {
"<space> Yes, read this newsgroup.",
" a About BARN.",
" c Catch up. Mark everything in this newsgroup as read.",
" n No, don't read this newsgroup.",
" q Quit ARN.",
" y Yes, read this newsgroup.",
(char *) NULL
};
ReadNewsgroup( ng, gkill )
NG_INFO *ng;
KILL_INFO **gkill;
{
KILL_INFO *lkill; /* local (within newsgroup) kill list */
DIR *dir; /* pointer to directory structure */
# ifdef sun
struct dirent *info; /* pointer to each file's info */
# else /* not sun, Amiga */
struct direct *info;
# endif /* sun */
struct stat stats; /* file system information about file */
ARTICLE_INFO *todo; /* articles to be read */
ARTICLE_INFO *art; /* info on eligible article */
ARTICLE_INFO **where; /* where to link in next eligible article */
ARTICLE_INFO *tmp;
char *fn; /* name of file currently being checked */
int narticles; /* number of unread articles in newsgroup */
char *kill_file; /* name of local kill file */
ARTICLE_INFO *CheckArticle();
int rc = FALSE, done = FALSE;
void ReadArticles(), SortArticles(), UpdateReadList();
if ( chdir( ng->name ) != 0 )
fprintf( stderr, "Newsgroup directory %s missing!\n", ng->name );
else
{
printf( "Checking %s ...\n", ng -> name );
if ( ( kill_file = GetVar( VAR_KILL ) ) == NULL )
kill_file = DEFAULT_KILL;
lkill = GetKillList( kill_file );
/*
* For each file in directory, check vs. already read (ng), check
* vs. global kill (gkill), check vs. local kill (lkill). If killed,
* add to already read (ng). If not read and not killed, put on
* unread list (todo).
*/
# ifdef sun
if ( ( ( dir = opendir( "." ) ) == NULL ) ||
# else /* Amiga */
if ( ( ( dir = opendir( "" ) ) == NULL ) ||
# endif
( info = readdir( dir ) ) == NULL )
printf( "%s is empty.\n", ng->name );
else
{
todo = NULLP( ARTICLE_INFO );
where = &todo;
narticles = 0;
do {
/*
* If filename starts with "KILL", this is a KILL file, not a
* news article. Don't open anything but regular files.
*/
if ( strncmp( fn = info -> d_name, KILL, strlen(KILL) ) != 0 &&
stat( fn, &stats ) == 0 && ( stats.st_mode & S_IFREG ) )
if ( art = CheckArticle( fn, ng->markers, *gkill, lkill ) )
{
*where = art;
where = & (*where)->next;
++narticles;
}
} while ( ( info = readdir( dir ) ) != NULL );
closedir( dir );
/* talk to user */
if ( narticles == 0 )
printf( "%s has no unread articles.\n", ng->name );
else while ( !done )
{
printf( "%s has %d unread articles. Read now? ", ng->name, narticles );
switch ( getcmd( READ_CMDS, READ_HELP, "" ) ) {
case ' ':
case 'y':
SortArticles( &todo, narticles );
for(art = todo; art != NULLP(ARTICLE_INFO); art = art->next)
if ( art->beenread )
--narticles;
ReadArticles( ng, todo, narticles, gkill, &lkill );
printf( "\n" );
UpdateReadList( ng, todo );
done = TRUE;
break;
case 'q':
rc = TRUE;
printf( "\n" );
done = TRUE;
break;
case 'c': /* catch up by marking all articles read */
printf( "\n" );
for ( tmp = todo; tmp != NULLP( ARTICLE_INFO ); tmp = tmp->next )
tmp->beenread = TRUE;
UpdateReadList( ng, todo );
done = TRUE;
break;
case 'a':
AboutBARN();
break;
case 'n': /* don't read now, just goto cleanup below */
printf( "\n" );
done = TRUE;
break;
default:
break;
}
}
/* kill any remaining todo list */
for ( ; todo != NULLP( ARTICLE_INFO ); todo = tmp )
{
tmp = todo->next;
DestroyArticle( todo );
}
}
/*
* Write the local kill file to disk in case we changed it.
*/
PutKillList( kill_file, lkill );
DestroyKillList( lkill );
}
return rc;
}
/****************************************************************************/
/* FUNCTION: CheckArticle */
/* */
/* PURPOSE: Check readability of an article. */
/* */
/* INPUT PARAMETERS: */
/* NAME I/O DESCRIPTION */
/* ---- --- ----------- */
/* filename I Name of file containing article to be checked. */
/* markers I Pointer to list of articles already read. */
/* gkill I Global kill list. */
/* lkill I Local kill list. */
/* */
/* RETURNS: */
/* (ARTICLE_INFO *) Pointer to article, if readable. */
/* (NULL) If not readable. */
/* */
/* COMMENTS: */
/* */
/* HISTORY: */
/* 1. 04 Sep 89 Created. */
/* 2. 30 Dec 89 Implemented kill-list checking. */
/* 3. 22 Oct 90 Changed from string compare to regular */
/* expression match. */
/* 4. 24 Oct 90 Tells what it killed and why. */
/* */
/****************************************************************************/
ARTICLE_INFO *CheckArticle( filename, markers, gkill, lkill )
char *filename;
MARKER *markers;
KILL_INFO *gkill, *lkill;
{
long article_number;
ARTICLE_INFO *art;
HEADER_INFO *hdr;
KILL_INFO *kp;
int done = FALSE;
article_number = atol( filename );
if ( NumberCovered( markers, article_number ) )
return NULLP( ARTICLE_INFO );
if ( ( art = ParseArticle( filename ) ) == NULL )
return art;
/*
* Check kill lists.
*/
for ( hdr = art->headers; hdr != NULLP(HEADER_INFO) && !done; hdr = hdr->next )
{
for ( kp = gkill; kp != NULLP( KILL_INFO ) && !done; kp = kp->next )
if ( strcmp( hdr->fieldname, kp->fieldname ) == 0 &&
regexec( kp -> pattern, hdr -> fieldvalue ) == 1 )
{
done = art->beenread = TRUE;
printf( "Killed %ld, %s: %s\n", art -> number,
hdr -> fieldname, hdr -> fieldvalue );
}
for ( kp = lkill; kp != NULLP( KILL_INFO ) && !done; kp = kp->next )
if ( strcmp( hdr->fieldname, kp->fieldname ) == 0 &&
regexec( kp -> pattern, hdr -> fieldvalue ) == 1 )
{
done = art->beenread = TRUE;
printf( "Killed %ld, %s: %s\n", art -> number,
hdr -> fieldname, hdr -> fieldvalue );
}
}
return art;
}
/****************************************************************************/
/* FUNCTION: FindNextArticle */
/* */
/* PURPOSE: Find next article on same subject thread. */
/* */
/* INPUT PARAMETERS: */
/* NAME I/O DESCRIPTION */
/* ---- --- ----------- */
/* current I Last article displayed. */
/* sub I Subject line of last article. */
/* found O Indicate whether or not we found another art. */
/* top I Head of list of articles. */
/* */
/* RETURNS: */
/* (ARTICLE_INFO *) next article on subject thread. */
/* */
/* COMMENTS: */
/* Returns "current" if no match is found. */
/* */
/* HISTORY: */
/* 1. 14 Nov 90 Created. */
/* */
/****************************************************************************/
ARTICLE_INFO *FindNextArticle( current, sub, found, top )
ARTICLE_INFO *current;
char *sub;
int *found;
ARTICLE_INFO *top;
{
ARTICLE_INFO *p;
char temp[MAXLINE];
struct regexp *subre; /* regular expression pattern for subj match */
*found = FALSE;
if ( ( subre = MakePattern( temp, sub ) ) == NULL )
fprintf( stderr, "Can't compile regex '%s'\n", temp );
for ( p = current->next; p != NULLP( ARTICLE_INFO ) && p != current; )
{
if ( ! p->beenread )
if ( p -> subject )
if ( regexec( subre, p -> subject ) == 1 )
{
current = p;
*found = TRUE;
}
if ( *found )
break;
if ( ( p = p->next ) == NULLP( ARTICLE_INFO ) )
p = top;
}
if ( subre != NULL )
free( (char *) subre );
return p;
}
/****************************************************************************/
/* FUNCTION: ReadArticles */
/* */
/* PURPOSE: Allow user to read articles within a newsgroup. */
/* */
/* INPUT PARAMETERS: */
/* NAME I/O DESCRIPTION */
/* ---- --- ----------- */
/* ng I/O List of articles read. */
/* todo I/O List of articles to be read. */
/* narticles I Number of unread articles. */
/* gkill I/O Global kill list. */
/* lkill I/O Local kill list. */
/* */
/* RETURNS: */
/* */
/* COMMENTS: */
/* */
/* HISTORY: */
/* 1. 04 Sep 89 Created. */
/* 2. 17 Oct 90 Added '-' command to go to previous article. */
/* 3. 10 Nov 90 Find next art from beginning if not following */
/* Subject thread. Makes sure articles get shown */
/* in order of posting. */
/* 4. 28 Nov 90 Added "/" to let user specify Subject to match. */
/* 5. 05 Jan 91 Added "#" to go to specific article number. */
/* 6. 06 Jan 91 Added "a" command for About BARN information. */
/* */
/****************************************************************************/
# define CMDS "acFfjKkmnqRrwy-=/# "
static char *CMDS_HELP[] = {
" a About BARN.",
" c Catch up. Mark all articles in this newsgroup as read.",
" Ff Followup, 'F' includes current article.",
" Kk Kill articles with this Subject, 'K' adds to KILL file.",
" m Mark this article as unread.",
" n,j Mark this article as read.",
" q Quit this newsgroup.",
" Rr Reply via mail, 'R' includes current article.",
" w Write this article. Prompts for filename.",
" y,<sp> Read this article.",
" - Go to previous article, marking it as unread.",
" = Scan subjects.",
" / Find next article with subject entered.",
" # Go to numbered article.",
(char *) NULL
};
void ReadArticles( ng, todo, narticles, gkill, lkill )
NG_INFO *ng;
ARTICLE_INFO *todo;
int narticles;
KILL_INFO **gkill, **lkill;
{
int cmd; /* user's command */
ARTICLE_INFO *current; /* article currently being examined */
ARTICLE_INFO *previous = NULLP( ARTICLE_INFO ); /* last art. examined */
ARTICLE_INFO *last; /* used to track previous */
ARTICLE_INFO *p;
KILL_INFO *tmp;
char temp[BUFSIZ], buf[BUFSIZ], *sub, followsub[BUFSIZ];
int readit; /* did article get read? */
int found; /* found another article with same subject? */
int follow = FALSE; /* following subject thread? */
int lines; /* lines used on screen for header */
struct regexp *subre; /* regular expression pattern for subj match */
void DisplayHeaders();
ARTICLE_INFO *FindNumberedArticle();
current = todo;
while ( narticles > 0 )
{
while ( current->beenread )
if ( ( current = current->next ) == NULLP( ARTICLE_INFO ) )
current = todo;
Clear_Screen;
printf( "Article %ld (%d more) in %s\n", current->number, narticles - 1, ng->name );
lines = 1;
DisplayHeaders( current, &lines, &sub );
printf( "\nRead? " );
++lines;
readit = FALSE;
last = current;
if ( ( cmd = getcmd( CMDS, CMDS_HELP, "" ) ) == ' ' || cmd == 'y' )
{
cmd = Paginate( current, lines );
readit = TRUE;
}
found = FALSE;
switch ( cmd ) {
case ' ':
case 'y':
current->beenread = TRUE;
if ( --narticles == 0 )
break;
/*
* Find next article with same subject.
*/
if ( !follow )
strcpy( followsub, sub );
current = FindNextArticle( current, followsub, &found, todo );
follow = found;
break;
case 'a':
AboutBARN();
found = TRUE; /* don't let us go to top of newsgroup */
break;
case '/':
cooked( stdin );
printf( "/" );
gets( buf );
current = FindNextArticle( current, buf, &found, todo );
if ( follow = found )
strcpy( followsub, buf );
else
printf( "Not found.\n" );
raw( stdin );
break;
case '#':
cooked( stdin );
printf( "#" );
gets( buf );
current = FindNumberedArticle( todo, (long) atol( buf ) );
if ( current == NULLP( ARTICLE_INFO ) )
{
printf( "Not found.\n" );
current = last;
}
else if ( current -> beenread )
{
current -> beenread = FALSE;
++narticles;
}
raw( stdin );
found = TRUE; /* so we don't start at top of newsgroup */
break;
case 'k':
case 'K':
/* kill */
current->beenread = TRUE;
--narticles;
printf( "\nKilling %s ...\n", sub );
if ( ( subre = MakePattern( temp, sub ) ) == NULL )
fprintf( stderr, "Can't compile regex '%s'\n", temp );
for ( p = current->next; p != NULLP( ARTICLE_INFO ) && p != current; )
{
if ( ! p->beenread )
if ( p -> subject && regexec( subre, p -> subject ) == 1 )
{
p->beenread = TRUE;
--narticles;
}
if ( ( p = p->next ) == NULLP( ARTICLE_INFO ) )
p = todo;
}
if ( cmd == 'K' )
{
printf( "Adding to newsgroup KILL file..." );
tmp = (KILL_INFO *) malloc( sizeof( KILL_INFO ) );
tmp->fieldname = strdup( HDR_SUBJECT );
tmp->fieldvalue = strdup( temp );
tmp->pattern = subre;
tmp->next = *lkill;
*lkill = tmp;
printf( "Done\n" );
}
else if ( subre != NULL )
free( (char *) subre );
follow = FALSE;
break;
case 'n':
case 'j':
/*
* Mark this article as read and move on to the next one with
* the same subject.
*/
current->beenread = TRUE;
--narticles;
/* FALL THROUGH */
case 'm':
/*
* "Mark as unread". This simply goes on to the next unread
* article without marking this one as read. Better marking
* routines are possible.
*/
if ( !follow )
strcpy( followsub, sub );
current = FindNextArticle( current, followsub, &found, todo );
follow = found;
if ( !found && cmd == 'm' )
{
if ( ( current = current -> next ) == NULLP( ARTICLE_INFO ) )
current = todo;
}
else if ( !found )
current = todo;
break;
case 'q':
if ( readit )
current->beenread = TRUE;
narticles = 0; /* break out of while loop */
break;
case 'c': /* catch up by marking all articles read */
for ( current = todo; narticles > 0; current = current->next )
{
if ( ! current->beenread )
--narticles;
current->beenread = TRUE;
}
break;
case 'w': /* write article to top directory */
if ( readit )
{
current->beenread = TRUE;
--narticles;
}
cooked( stdin );
printf( "\nFilename: " );
gets( buf );
raw( stdin );
sprintf( temp, "%s %ld %s/%s", COPY_CMD, current->number,
currentdir, buf );
system( temp );
break;
case 'r':
Reply( current, FALSE );
break;
case 'R':
Reply( current, TRUE );
break;
case 'f':
Followup( ng -> name, current, FALSE );
break;
case 'F':
Followup( ng -> name, current, TRUE );
break;
case '-':
if ( previous != NULL )
{
current = previous;
if ( current -> beenread )
{
current -> beenread = FALSE;
++narticles;
}
}
break;
case '=': /* subject scan */
ScanSubjects( todo );
break;
default:
break;
}
previous = last;
if ( !found )
current = todo; /* if not following thread, start at top */
}
}
/****************************************************************************/
/* FUNCTION: DisplayHeaders */
/* */
/* PURPOSE: Print interesting article headers. */
/* */
/* INPUT PARAMETERS: */
/* NAME I/O DESCRIPTION */
/* ---- --- ----------- */
/* current I Pointer to current article. */
/* lines O Pointer to count of lines printed on screen. */
/* sub O Pointer to current subject pointer. */
/* */
/* RETURNS: none */
/* */
/* COMMENTS: */
/* Prints all headers stored in the articles header list, keeping */
/* track of the number of lines printed on the screen and returning */
/* that in 'lines'. When the 'Subject' header is encountered, a */
/* pointer to its value is stored in 'sub'. */
/* */
/* HISTORY: */
/* 1. 05 Jan 91 Factored out of ReadArticles(). */
/* */
/****************************************************************************/
void DisplayHeaders( current, lines, sub )
ARTICLE_INFO *current;
int *lines;
char **sub;
{
HEADER_INFO *ptr;
for ( ptr = current->headers; ptr != NULLP( HEADER_INFO ); ptr = ptr->next )
{
++*lines;
if ( strcmp( ptr->fieldname, HDR_SUBJECT ) == 0 )
{
*sub = ptr->fieldvalue;
printf( "%s: ", ptr -> fieldname );
UL_ON;
printf( "%s", *sub );
UL_OFF;
printf( "\n" );
if ( strlen( ptr->fieldname ) + strlen( *sub ) + 2 > Columns )
++*lines;
}
else
{
printf( "%s: %s\n", ptr->fieldname, ptr->fieldvalue );
if ( strlen( ptr->fieldname ) + strlen( ptr->fieldvalue ) + 2 > Columns )
++*lines;
}
}
}
/****************************************************************************/
/* FUNCTION: FindNumberedArticle */
/* */
/* PURPOSE: Return pointer to article identified by given number. */
/* */
/* INPUT PARAMETERS: */
/* NAME I/O DESCRIPTION */
/* ---- --- ----------- */
/* head I Pointer to head of article list. */
/* number I Number of article to find. */
/* */
/* RETURNS: */
/* NULL No such numbered article. */
/* ARTICLE_INFO *ptr Pointer to numbered article. */
/* */
/* COMMENTS: */
/* */
/* HISTORY: */
/* 1. 05 Jan 91 Created. */
/* */
/****************************************************************************/
ARTICLE_INFO *FindNumberedArticle( head, number )
ARTICLE_INFO *head;
long number;
{
ARTICLE_INFO *p;
for ( p = head; p != NULLP( ARTICLE_INFO ); p = p -> next )
if ( p -> number == number )
return p;
else if ( p -> number > number )
break;
return NULLP( ARTICLE_INFO );
}
/****************************************************************************/
/* FUNCTION: SortArticles */
/* */
/* PURPOSE: Sort article list by article number. */
/* */
/* INPUT PARAMETERS: */
/* NAME I/O DESCRIPTION */
/* ---- --- ----------- */
/* arts I/O Pointer to list of articles. */
/* narticles I Number of articles in list. */
/* */
/* RETURNS: none */
/* */
/* COMMENTS: */
/* *arts is modified. */
/* */
/* HISTORY: */
/* 1. 04 Sep 89 Created. */
/* */
/****************************************************************************/
void SortArticles( arts, narticles )
ARTICLE_INFO **arts;
int narticles;
{
ARTICLE_INFO *ptr;
ARTICLE_INFO **where;
SORT_INFO *articles;
int i;
int Compare();
articles = (SORT_INFO *) malloc( sizeof( SORT_INFO ) * narticles );
for ( ptr = *arts, i = 0; i < narticles; i++, ptr = ptr->next )
{
articles[i].pointer = ptr;
articles[i].num = ptr->number;
}
qsort( (char *) articles, narticles, sizeof( SORT_INFO ), Compare );
where = arts;
for ( i = 0; i < narticles; i++ )
{
*where = articles[i].pointer;
where = &(*where)->next;
}
*where = NULLP( ARTICLE_INFO );
free( articles );
}
/****************************************************************************/
/* FUNCTION: Compare */
/* */
/* PURPOSE: Help function to sort articles. */
/* */
/* INPUT PARAMETERS: */
/* NAME I/O DESCRIPTION */
/* ---- --- ----------- */
/* as defined by qsort() */
/* */
/* RETURNS: */
/* as defined by qsort() */
/* */
/* COMMENTS: */
/* */
/* HISTORY: */
/* 1. 04 Sep 89 Created. */
/* */
/****************************************************************************/
Compare( first, second, size )
SORT_INFO *first, *second;
int size;
{
if ( first->num < second->num )
return -1;
else if ( first->num > second->num )
return 1;
else
return 0;
}
/****************************************************************************/
/* FUNCTION: getcmd */
/* */
/* PURPOSE: Get a command from the user. */
/* */
/* INPUT PARAMETERS: */
/* NAME I/O DESCRIPTION */
/* ---- --- ----------- */
/* cmdlist I List of valid commands. */
/* help I Array of help strings to display for user. */
/* otherlist I List of other valid commands that aren't shown */
/* to the user. */
/* */
/* RETURNS: */
/* */
/* COMMENTS: */
/* The commands string must not allow 'h', 'H', or '?' as these are */
/* used to view the help string. */
/* */
/* HISTORY: */
/* 1. 04 Sep 89 Created. */
/* 2. 17 Oct 90 Added help options. */
/* 3. 14 Nov 90 Added CR/NL. */
/* 4. 28 Nov 90 Made "help" array of strings to escape compiler */
/* string length limitations. */
/* 5. 06 Jan 91 Generalized special cases of CR/NL to otherlist.*/
/* */
/****************************************************************************/
getcmd( cmdlist, help, otherlist )
char *cmdlist;
char **help;
char *otherlist;
{
int ch;
char **p;
ch = '"';
printf( "[%s%s] ", cmdlist, ( strchr( otherlist, (char) '\r' ) ? "<CR>" : "" ) );
while ( strchr( cmdlist, (char) ch ) == NULLP( char ) )
{
ch = getchar();
if ( ch == 'h' || ch == 'H' || ch == '?' ) /* user wants help */
{
INVERSE_OFF;
printf( "\n" );
for ( p = help; *p != (char *) NULL; p++ )
printf( "%s\n", *p );
printf( "\n" );
printf( "[%s%s] ", cmdlist, ( strchr( otherlist, (char) '\r' ) ? "<CR>" : "" ) );
}
else if ( strchr( otherlist, (char) ch ) != NULLP( char ) )
break;
}
return ch;
}
/****************************************************************************/
/* FUNCTION: Paginate */
/* */
/* PURPOSE: Display article one page at a time. */
/* */
/* INPUT PARAMETERS: */
/* NAME I/O DESCRIPTION */
/* ---- --- ----------- */
/* article I Article to be displayed. */
/* lines I Lines already used on display. */
/* */
/* RETURNS: */
/* Last command character hit by user if it needs to be processed */
/* by caller. */
/* */
/* COMMENTS: */
/* */
/* HISTORY: */
/* 1. 28 Sep 89 Created. */
/* 2. 30 Dec 89 Return command letter hit at end of article. */
/* 3. 12 Sep 90 Added parm for lines already printed on display.*/
/* 4. 24 Oct 90 Added percentage read display. */
/* 5. 14 Nov 90 Added CR/NL to print one more line. */
/* 6. 22 Nov 90 End page at form feed. */
/* 7. 25 Nov 90 Added noscroll. If set in config file, screen */
/* will be cleared for each new page rather than */
/* scrolling. */
/* 8. 05 Jan 91 Fixed line-wrap calculation. */
/* 9. 05 Jan 91 Put last line of previous page at top of next */
/* page in NO_SCROLL mode (for continuity). */
/* 10. 06 Jan 91 Added "b" - go back one page. */
/* 11. 06 Jan 91 Added "a" - About BARN. */
/* */
/****************************************************************************/
# define PAGE_CMDS " abFfjKkmnqRrw"
# define KICK_CMDS "kKwRrFfmn" /* get kicked up to calling function */
static char *PAGE_HELP[] = {
"<space> View next page of article.",
"<return> Next line.",
" a About BARN.",
" b,<bs> Go back one page.",
" F,f Followup, 'F' includes current article.",
" j,n,q Quit the pager, marking the article as read.",
" K Same as 'k', and adds Subject to KILL file.",
" k Kill (mark as read) all articles w/this Subject.",
" m Mark as unread.",
" R,r Reply via mail, 'R' includes current article.",
" w Write the article to disk. Prompts for a filename.",
(char *) NULL
};
Paginate( article, lines )
ARTICLE_INFO *article;
int lines;
{
char filename[MAXLINE], buf[BUFSIZ], lastbuf[BUFSIZ], *readstatus;
FILE *fp;
int line_number; /* what line of display the cursor is on */
int cmd, rc = ' ';
int length;
char *p;
printf( "\r%*s\r", Columns, " " );
sprintf( filename, "%ld", article->number );
if ( ( fp = fopen( filename, "r" ) ) == NULLP( FILE ) )
printf( "ARTICLE UNREADABLE!\n" );
else
{
fseek( fp, 0L, 2 );
length = ftell( fp );
fseek( fp, article->textpos, 0 );
line_number = lines;
while ( ( readstatus = fgets( buf, BUFSIZ, fp ) ) != NULLP( char ) )
{
if ( buf[strlen(buf)-1] == '\n' ) /* remove trailing newline */
buf[strlen(buf)-1] = NULL;
line_number += 1 + ( strlen( buf ) - 1 ) / Columns;
if ( line_number > ( Lines - 1 ) || strchr( buf, '\f' ) != NULL )
{
/* inverse video please */
INVERSE_ON;
printf( "-- more (%d%%) --", (int) ((ftell( fp ) * 100) / length) );
cmd = getcmd( PAGE_CMDS, PAGE_HELP, "\r\n\b" );
INVERSE_OFF;
printf( "\r%40s\r", " " );
if ( ( p = strchr( buf, '\f' ) ) != NULL )
*p = ' ';
if ( cmd == ' ' )
{
line_number = 0;
if ( GetVar( VAR_NOSCROLL ) != NULL )
{
Clear_Screen;
printf( "%s\n", lastbuf );
line_number += 1 + ( strlen( lastbuf ) - 1 ) / Columns;
}
line_number += 1 + ( strlen( buf ) - 1 ) / Columns;
printf( "%s\n", buf );
}
else if ( cmd == '\n' || cmd == '\r' )
/*
* Print one line. Don't adjust line_number because we
* want to prompt again for next line.
*/
printf( "%s\n", buf );
else if ( cmd == 'b' || cmd == '\b' )
{
long currentpos, newpos;
char *scratch, *ptr;
int size, lines;
/* go back one page */
line_number = 0;
currentpos = ftell( fp ) - strlen( buf ) - 1;
newpos = currentpos - ( Lines * 2 ) * ( Columns + 1 );
if ( newpos < article -> textpos )
newpos = article -> textpos;
fseek( fp, newpos, 0 );
size = ( currentpos - ftell( fp ) ) * sizeof( char );
scratch = malloc( size );
(void) fread( scratch, sizeof( char ), size, fp );
ptr = scratch + size - 1;
lines = 0;
while ( ptr > scratch && lines < ( Lines - 1 ) * 2 )
{
if ( *ptr == '\n' )
++lines;
--ptr;
}
if ( ptr > scratch )
newpos += ( ptr + 2 - scratch );
fseek( fp, newpos, 0 );
free( scratch );
if ( GetVar( VAR_NOSCROLL ) != NULL )
{
Clear_Screen;
}
}
else if ( cmd == 'a' )
{
AboutBARN();
/*
* Fool this fn into re-reading the same line and printing
* the prompt again.
*/
fseek( fp, 0 - ( strlen( buf ) + 1 ), 1 );
strcpy( buf, lastbuf );
}
else
{
if ( strchr( KICK_CMDS, (char) cmd ) != NULL )
rc = cmd; /* these commands get kicked up to caller */
break;
}
}
else
printf( "%s\n", buf );
strcpy( lastbuf, buf );
}
fclose( fp );
if ( readstatus == NULLP( char ) )
{
INVERSE_ON;
printf( "End of article %ld. ", article->number );
cmd = getcmd( PAGE_CMDS, PAGE_HELP, "" );
INVERSE_OFF;
if ( strchr( KICK_CMDS, (char) cmd ) != NULL )
rc = cmd; /* these commands get kicked up to caller */
}
}
return rc;
}
/****************************************************************************/
/* FUNCTION: MakePattern */
/* */
/* PURPOSE: Attempt to create a valid regular expression pattern. */
/* */
/* INPUT PARAMETERS: */
/* NAME I/O DESCRIPTION */
/* ---- --- ----------- */
/* pattern O Put string form of pattern here. */
/* text I Text to be matched. */
/* */
/* RETURNS: */
/* (struct regexp *) Regular expression internal form. */
/* (NULL) Couldn't grok pattern. */
/* */
/* COMMENTS: */
/* Finds characters with special meaning to regexp and escapes them by */
/* preceding them with a backslash. */
/* */
/* Pattern had better be large enough to hold the resulting string. */
/* */
/* HISTORY: */
/* 1. 22 Oct 90 Created. */
/* 2. 28 Nov 90 Added backslash as char that needs to be */
/* backslashed. */
/* Match random text between optional 'Re:' stuff */
/* and 'text' pattern. */
/* */
/****************************************************************************/
struct regexp *MakePattern( pattern, text )
char *pattern;
char *text;
{
char temp[BUFSIZ]; /* translated string pattern */
char base[BUFSIZ]; /* string without Re: stuff */
char *p, *pbase;
struct regexp *remover_re;
struct regexp *re; /* resulting regular expression */
int len;
# ifdef DEBUG_REGSUB
int i;
# endif
strcpy( base, "\\1" );
if ( ( remover_re = regcomp( REPLY_REMOVER ) ) == NULL )
fprintf( stderr, "Can't compile REPLY_REMOVER\n" );
# ifdef DEBUG_REGSUB
if ( regexec( remover_re, text ) == 1 )
for ( i = 0; i < 10; i++ )
printf( "%d '%*s'\n", i, remover_re -> endp[1] - remover_re -> startp[1],
remover_re -> startp[1] );
regsub( remover_re, text, base );
printf( "but base is '%s'\n", base );
getchar();
# else
(void) regexec( remover_re, text );
len = remover_re -> endp[1] - remover_re -> startp[1];
strncpy( base, remover_re -> startp[1], len );
base[len] = NULL;
# endif
p = temp; pbase = base;
while ( *pbase != NULL )
{
switch ( *pbase )
{
case '.':
case '*':
case '+':
case '(':
case ')':
case '[':
case ']':
case '^':
case '$':
case '-':
case '?':
case '|':
case '\\':
*p++ = '\\';
default:
*p++ = *pbase;
break;
}
pbase++;
}
*p = NULL;
/*
* Try to match the Re: stuff that followups generate.
*/
strcpy( pattern, REPLY_PATTERN );
strcat( pattern, ".*" );
strcat( pattern, temp );
re = regcomp( pattern );
if ( remover_re != NULL )
free( (char *) remover_re );
return re;
}
/****************************************************************************/
/* FUNCTION: ScanSubjects */
/* */
/* PURPOSE: List articles in newsgroup by subject. */
/* */
/* INPUT PARAMETERS: */
/* NAME I/O DESCRIPTION */
/* ---- --- ----------- */
/* arts I Header of list of articles in newsgroup. */
/* */
/* RETURNS: */
/* */
/* COMMENTS: */
/* */
/* HISTORY: */
/* 1. 24 Oct 90 Created. */
/* 2. 14 Nov 90 Added CR to print one more line. */
/* 3. 16 Nov 90 Bug fix: always showed first article even if */
/* it had already been read. */
/* */
/****************************************************************************/
# define SCAN_CMDS "q "
static char *SCAN_HELP[] = {
"<space> View next page of subjects.",
"<return> View one more line.",
" q Quit scanning subjects.",
(char *) NULL
};
void ScanSubjects( arts )
ARTICLE_INFO *arts;
{
ARTICLE_INFO *pbase; /* ptr to base article of subject thread */
ARTICLE_INFO *preply; /* tmep ptr to reply articles */
int n_replies; /* # of replies to a base article */
char *sub; /* subject */
char dummy[MAXLINE]; /* not used */
struct regexp *pat; /* pattern to match subjects */
int printed = 0; /* # subjects printed this screenful */
int cmd;
printf( "\n" );
for ( pbase = arts; pbase != NULL; pbase = pbase -> next )
if ( ! pbase -> beenread )
pbase -> done = FALSE;
else
pbase -> done = TRUE;
/*
* Find first unread base article.
*/
for ( pbase = arts; pbase != NULL && pbase -> done; pbase = pbase -> next ) ;
while ( pbase != NULL )
{
if ( pbase -> subject )
sub = strdup( pbase -> subject );
else
sub = strdup( "<None>" );
pbase -> done = TRUE;
/*
* Count replies.
*/
n_replies = 0;
pat = MakePattern( dummy, sub );
for ( preply = pbase -> next; preply != NULL; preply = preply -> next )
if ( ! preply -> done && preply -> subject &&
regexec( pat, preply -> subject ) == 1 )
{
n_replies++;
preply -> done = TRUE;
}
printf( "%5ld: (%2d replies) %-55.55s\n", pbase -> number, n_replies, sub );
if ( pat != NULL )
free( (char *) pat );
if ( sub != NULL )
free( sub );
/*
* Find next base article we haven't processed.
*/
for ( pbase = pbase -> next; pbase != NULL && pbase -> done;
pbase = pbase -> next )
;
if ( ++printed >= Lines - 1 )
{
/* inverse video please */
INVERSE_ON;
printf( "--more--" );
cmd = getcmd( SCAN_CMDS, SCAN_HELP, "\r\n" );
INVERSE_OFF;
printf( "\r%40s\r", " " );
if ( cmd != '\n' && cmd != '\r' )
printed = 0;
if ( cmd == 'q' )
break;
}
}
if ( printed > 0 )
{
/* inverse video please */
INVERSE_ON;
printf( "--more--" );
(void) getcmd( SCAN_CMDS, SCAN_HELP, "" );
INVERSE_OFF;
printf( "\r%40s\r", " " );
}
for ( pbase = arts; pbase != NULL; pbase = pbase -> next )
if ( ! pbase -> beenread )
pbase -> done = FALSE;
else
pbase -> done = TRUE;
}